#include <game/ScreenDefinition.h>
#include <game/Screen.h>
#include <game/GameDefinition.h>
#include <game/ScreenBackend.h>
#include <game/ScreenBackendFactory.h>
#include <game/CameraDefinition.h>
#include <game/ResourceReferenceDefinition.h>
#include <game/controls/Control.h>
#include <game/controls/ControlCollection.h>
#include <game/controls/ControlCollectionLoader.h>
#include <game/XMLProperties.h>

#include <Gluck.h>
#include <xml/Stream.h>
#include <lib3d/node/Node.h>
#include <lib3d/node/Root.h>
#include <lib3d/node/Camera.h>
#include <lib3d/node/Factory.h>
#include <resource/Manager.h>

using namespace liba::lib3d;

ARELLO_GAME_NAMESPACE_DECL_START

ScreenDefinition::ScreenDefinition()
{
}

ScreenDefinition::~ScreenDefinition()
{
}

class SceneLoader
	: public xml::ObjectANSI
{
public:
	SceneLoader() : root(new node::Root(Atom(""))) {}
	virtual ~SceneLoader() {}

	virtual bool on_node(xml::Provider * prov, const std::string & name )
	{
		if (name == "Camera")
		{
			if (!this->root.get())
				THROW(Gluck(__FUNCTION__, "Scene root node doesn't exist when trying to create a camera"));

			CameraDefinition cam;
			cam.SetSceneRoot(this->root.get());

			if (!prov->parse_object(cam.GetLoader()))
				return false;

			this->camera = cam.ReleaseCamera();
			return true;
		}
		if (name == "Resource")
		{
			ResourceReferenceDefinition res;
			if (!prov->parse_object(res.GetLoader()))
				return false;

			this->rootScenePath.reset(new filesystem::String(res.GetPath()));
			tryLoadRoot();

			return true;
		}

		return false;
	}

	virtual bool on_attribute(xml::Provider * prov, const std::string & name, const std::string & value )
	{
		if (name == "RootName")
		{
			this->root->SetName(Atom(value));
			return true;
		}

		return false;
	}

	std::auto_ptr<node::Node> ReleaseRoot() { return root; }
	std::auto_ptr<node::Camera> ReleaseCamera() { return camera; }

private:
	void tryLoadRoot()
	{
		if (!this->rootScenePath.get())
			return;

		std::auto_ptr<resource::Resource> scene_res(resource::Manager::create_resource(*this->rootScenePath.get()));
		if (!xml::StreamResource(scene_res.get()).load(&xml::Root(root.get(), "Scene")))
			THROW(GluckBadResource(__FUNCTION__, "load() failed", *this->rootScenePath.get()));

		this->rootScenePath.reset();
	}

	std::auto_ptr<filesystem::String> rootScenePath;
	std::auto_ptr<node::Node> root;
	std::auto_ptr<node::Camera> camera;
};

bool ScreenDefinition::on_node(xml::Provider * prov, const std::string & name)
{
	if (name == "Scene")
	{
		SceneLoader loader;
		if (!prov->parse_object(&loader))
			return false;

		this->rootNode = loader.ReleaseRoot();
		this->mainCamera = loader.ReleaseCamera();

		return true;
	}
	if (name == "Controls")
	{
		controls::ControlCollectionLoader ldr;
		if (!this->rootNode.get())
			THROW(Gluck(__FUNCTION__, "Cannot load controls if a root screen node isn't defined"));
		ldr.SetRootNode(this->rootNode.get());
		ldr.SetControlFactory(this->controlFactory);
		
		if (!prov->parse_object(ldr.GetLoader()))
			return false;	

		const std::vector<boost::shared_ptr<controls::Control> >& loaderControls = ldr.GetLoadedControls();
		for (std::vector<boost::shared_ptr<controls::Control> >::const_iterator i = loaderControls.begin(); i != loaderControls.end(); ++i)
			this->controls.push_back(*i);

		return true;
	}
	if (name == "Properties")
	{
		properties.reset(new Properties());

		return prov->parse_object(properties.get());
	}

	return false;
}

bool ScreenDefinition::on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
{
	if (name == "BackendClass")
		return converter::convert(value, &this->backendClass);

	return false;
}

bool ScreenDefinition::on_end_node(xml::Provider * prov)
{
	if (!this->backendClass.empty())
	{
		this->backend = this->backendFactory.CreateBackend(this->backendClass);
		if (!this->backend.get())
		{
			std::string errorMessage("Unable to create screen backend class named \"");
			errorMessage += this->backendClass;
			errorMessage += "\"";
			THROW(Gluck(__FUNCTION__, errorMessage));
		}
	}

	return true;
}

std::auto_ptr<Screen> ScreenDefinition::CreateScreen(Root& root)
{
	if (!this->rootNode.get())
		THROW(Gluck(__FUNCTION__, "Unable to construct screen without a root node. Maybe you've double-called CreateScreen?"));

	std::auto_ptr<Screen> localScreen(new Screen(root));
	localScreen->SetRootNode(this->rootNode);
	localScreen->SetMainCamera(this->mainCamera);
	localScreen->SetProperties(this->properties);

	for (std::vector<boost::shared_ptr<controls::Control> >::const_iterator i = this->controls.begin(); i != this->controls.end(); ++i)
		localScreen->GetControls()->Add(*i);

	if (this->backend.get())
	{
		localScreen->AttachBackend(this->backend);
	}

	return localScreen;
}

ARELLO_GAME_NAMESPACE_DECL_END